package com.beta.auth.service.impl;

import com.alibaba.fastjson.JSON;
import com.beta.auth.config.UserAuthCfgBean;
import com.beta.auth.constants.AuthChannel;
import com.beta.auth.constants.BusinessConstants;
import com.beta.auth.constants.CacheConstans;
import com.beta.auth.dto.auth.AuthDto;
import com.beta.auth.dto.auth.WXAuthState;
import com.beta.auth.service.AbstractAuthService;
import com.beta.auth.utils.JwtUtil;
import com.beta.auth.utils.ResponseUtils;
import com.beta.cat.service.RedissonService;
import com.beta.user.auth.center.api.constant.UserAuthConstant;
import com.beta.user.auth.center.api.model.WxOauthUser;
import com.beta.user.auth.center.api.request.AddWxOauthUserReq;
import com.beta.user.auth.center.api.request.QueryWxOauthUserReq;
import com.beta.user.auth.center.api.request.UpdateWxOauthUserReq;
import com.beta.user.auth.center.api.response.AddWxOauthUserResp;
import com.beta.user.auth.center.api.response.QueryWxOauthUserResp;
import com.beta.user.auth.center.api.response.UpdateWxOauthUserResp;
import com.beta.user.auth.center.api.service.IRPCWxOauthUserService;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @author gwqin
 * @description 公众号授权实现
 * @date 2023-07-13 15:56
 */
@Slf4j
@Service
public class MpAuthServiceImpl extends AbstractAuthService {

    /**
     * 腾讯公众号基础（静默）授权传参
     */
    private static final String SCOPE_BASE = "snsapi_base";

    /**
     * 腾讯公众号高级（手动）授权传参
     */
    private static final String SCOPE_PRIVATE = "snsapi_userinfo";

    @Autowired
    private UserAuthCfgBean userAuthCfgBean;

    @Autowired
    private RedissonService redissonService;

    @Autowired
    private WxMpService wxMpService;

    @DubboReference
    private IRPCWxOauthUserService rpcWxOauthUserService;

    @Override
    public void invokeAuth(AuthDto authDto, WXAuthState authState, HttpServletRequest request, HttpServletResponse response) {
        try {
            // 1.校验参数合法性
            boolean result = super.checkAuthCode(authState, authDto, response);
            if (authDto.getAc() == -1) {
                return;
            }

            String redirectUrl;
            String authType = authState.getAuthType();

            // 校验授权类型是否合法
            String scope = null;
            if (UserAuthConstant.MP_AUTH_TYPE_BASE.equals(authType) || StringUtils.isEmpty(authType)) {
                scope = SCOPE_BASE;
            } else if (UserAuthConstant.MP_AUTH_TYPE_USER_INFO.equals(authType)) {
                scope = SCOPE_PRIVATE;
            } else {
                log.warn("暂不支持的授权类型，authType = {}", authType);
                ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.PARAMS_INVALID_CODE, response);
                return;
            }

            if (!result) {
                // 2.首次调用接口，构造公众号授权链接
                redirectUrl = wxMpService.getOAuth2Service().buildAuthorizationUrl(userAuthCfgBean.getMpRedirectUrl(), scope, authState.getState());
            } else {
                // 基础授权，获取openId
                // authAccount存放用户ID和签发时间
                Map<String, String> authAccount = new HashMap<>();
                // 签发时间
                long ct = System.currentTimeMillis();
                authAccount.put(CT, String.valueOf(ct));

                // bjwte为bjwt的过期时间，基础授权默认为1天，高级授权默认为30天
                AtomicLong bjwte = new AtomicLong(0);
                AtomicInteger uAuthFlag = new AtomicInteger(UserAuthConstant.MP_AUTH_TYPE_USER_INFO.equals(authType) ? UserAuthConstant.ADVANCED_AUTH_FLAG : UserAuthConstant.BASE_AUTH_FLAG);

                // 此处已经完成基础授权，获取到用户基本信息
                WxOAuth2AccessToken accessToken = wxMpService.getOAuth2Service().getAccessToken(authDto.getCode());
                if (accessToken == null) {
                    log.warn("获取到用户基本信息为null，authDto = {}", JSON.toJSONString(authDto));
                    ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.WX_ERROR_CODE, response);
                    return;
                }

                // 3.识别是基础授权还是高级授权
                if (StringUtils.isEmpty(authType) || StringUtils.equals(authType, UserAuthConstant.MP_AUTH_TYPE_BASE)) {
                    // 基础授权相关操作
                    if (!invokeBaseAuth(accessToken, authAccount, bjwte, uAuthFlag)) {
                        ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.RPC_ERROR_CODE, response);
                        return;
                    }
                } else if (StringUtils.equals(authType, UserAuthConstant.MP_AUTH_TYPE_USER_INFO)
                        && !invokeUserInfoAuth(accessToken, authAccount, bjwte)) {
                    // 高级授权相关操作
                    ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.RPC_ERROR_CODE, response);
                    return;
                }
                /*暂时不考虑高级授权，该字段暂不返回
                + "&uAuthFlag=" + uAuthFlag.get();*/
                String bjwt = JwtUtil.encrypt(authAccount, userAuthCfgBean.getMpJwtSecret(), bjwte.get() - ct);
                redirectUrl = authState.getReturnUrl()
                        + "#"
                        + "ustate=" + authState.getUstate()
                        + "&bjwt=" + bjwt
                        + "&bjwte=" + bjwte.get();

                String cacheKey = String.format(CacheConstans.BJWT_WX_IN_SESSION_VALIDITY, authAccount.get(USERID), authState.getClientType());
                redissonService.set(cacheKey, bjwt, userAuthCfgBean.getSessionMaxInterval() * 1000L);
            }

            // 重定向到错误页面
            if (StringUtils.isBlank(redirectUrl)) {
                log.warn("公众号授权，redirectUrl为空，authDto = {}", JSON.toJSONString(authDto));
                ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.UNKNOWN_ERROR_CODE, response);
                return;
            }

            // 4.根据情况，重定向到公众号授权地址进行授权
            // 或者重定向到跳转地址并携带token
            ResponseUtils.sendRedirect(response, userAuthCfgBean.getSafeHosts(), redirectUrl);
        } catch (Exception e) {
            log.error("公众号授权，系统异常，异常信息：{}", e.getMessage(), e);
            ResponseUtils.toErrPage(BusinessConstants.SYSTEM_ERROR_MSG, BusinessConstants.UNKNOWN_ERROR_CODE, response);
        }
    }

    /**
     * 执行基础授权
     *
     * @param accessToken
     * @param authAccount
     * @param bjwte       bjwt过期时间
     * @param authType    授权类型
     * @return
     */
    private boolean invokeBaseAuth(WxOAuth2AccessToken accessToken, Map<String, String> authAccount, AtomicLong bjwte, AtomicInteger authType) {
        // 基础授权
        String openId = accessToken.getOpenId();
        String mpAppId = userAuthCfgBean.getMpAppId();
        WxOauthUser wxOauthUser = queryWxOauthUser(mpAppId, null, openId);

        // 判断是否已经完成过授权
        long ct = Long.parseLong(authAccount.get(CT));
        if (wxOauthUser.getId() == null) {
            // 首次授权，保存入库
            wxOauthUser = new WxOauthUser().setOpenId(openId).setAppId(mpAppId);
            AddWxOauthUserResp addResp = rpcWxOauthUserService.addWxOauthUser(new AddWxOauthUserReq().setWxOauthUser(wxOauthUser));
            if (addResp == null || addResp.getRetCodeInt() != BusinessConstants.SUCCESS_CODE) {
                log.error("公众号授权，新增授权账户异常，RPC接口返回：{}", JSON.toJSONString(addResp));
                return false;
            }

            authAccount.put(USERID, String.valueOf(addResp.getWxOauthUser().getId()));
            bjwte.set(LocalDateTime.ofInstant(Instant.ofEpochMilli(ct), ZoneId.systemDefault()).plusDays(userAuthCfgBean.getMpBaseAuthExpire()).toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
            log.info("完成公众号基础授权：{}", JSON.toJSONString(authAccount));
        } else {
            // 已经完成过授权
            authAccount.put(USERID, String.valueOf(wxOauthUser.getId()));

            LocalDateTime lastAdvancedTime = wxOauthUser.getLastAdvancedTime();
            // 判断是否完成过高级授权，且高级授权在有效期内
            if (wxOauthUser.getAdvancedAuthFlag() == UserAuthConstant.ADVANCED_AUTH_FLAG
                    && lastAdvancedTime.plusDays(userAuthCfgBean.getMpUerInfoAuthExpire()).isAfter(LocalDateTime.now())) {
                bjwte.set(lastAdvancedTime.plusDays(userAuthCfgBean.getMpUerInfoAuthExpire()).toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
                authType.set(UserAuthConstant.ADVANCED_AUTH_FLAG);
                log.info("已完成历史高级授权且在有效期内：{}", JSON.toJSONString(authAccount));
            } else {
                bjwte.set(LocalDateTime.ofInstant(Instant.ofEpochMilli(ct), ZoneId.systemDefault()).plusDays(userAuthCfgBean.getMpBaseAuthExpire()).toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
                log.info("未完成高级授权或高级授权已过期：{}", JSON.toJSONString(authAccount));
            }
        }

        return true;
    }

    /**
     * 执行高级授权
     *
     * @param accessToken
     * @param authAccount
     * @param bjwte
     * @return
     * @throws WxErrorException
     */
    private boolean invokeUserInfoAuth(WxOAuth2AccessToken accessToken, Map<String, String> authAccount, AtomicLong bjwte) throws WxErrorException {
        // 高级授权
        // 获取用户信息，unionid
        WxOAuth2UserInfo userInfo = wxMpService.getOAuth2Service().getUserInfo(accessToken, "zh_CN");
        String userId = null;

        WxOauthUser wxOauthUser = queryWxOauthUser(userAuthCfgBean.getMpAppId(), userInfo.getUnionId(), userInfo.getOpenid());

        wxOauthUser.setAppId(userAuthCfgBean.getMpAppId());
        wxOauthUser.setOpenId(userInfo.getOpenid());
        wxOauthUser.setUnionId(userInfo.getUnionId());
        wxOauthUser.setNickName(userInfo.getNickname());
        wxOauthUser.setSex(userInfo.getSex());
        wxOauthUser.setCity(userInfo.getCity());
        wxOauthUser.setProvince(userInfo.getProvince());
        wxOauthUser.setCountry(userInfo.getCountry());
        wxOauthUser.setHeadImgUrl(userInfo.getHeadImgUrl());
        wxOauthUser.setPrivileges(ArrayUtils.toString(userInfo.getPrivileges()));
        wxOauthUser.setSnapshotUser(userInfo.getSnapshotUser());
        wxOauthUser.setLastAdvancedTime(LocalDateTime.now());
        wxOauthUser.setAdvancedAuthFlag(UserAuthConstant.ADVANCED_AUTH_FLAG);

        if (wxOauthUser.getId() == null) {
            // 新增
            try {
                AddWxOauthUserResp addResp = rpcWxOauthUserService.addWxOauthUser(new AddWxOauthUserReq().setWxOauthUser(wxOauthUser));
                if (addResp == null || addResp.getRetCodeInt() != BusinessConstants.SUCCESS_CODE) {
                    log.error("公众号授权，新增授权账户异常，RPC接口返回：{}", JSON.toJSONString(addResp));
                    return false;
                }
                userId = String.valueOf(addResp.getWxOauthUser().getId());
            } catch (Exception e) {
                log.error("公众号授权，新增授权账户异常，异常信息：{}", e.getMessage(), e);
                return false;
            }
        } else {
            // 更新
            userId = String.valueOf(wxOauthUser.getId());
            try {
                UpdateWxOauthUserResp updateResp = rpcWxOauthUserService.updateWxOauthUser(new UpdateWxOauthUserReq().setWxOauthUser(wxOauthUser));
                if (updateResp == null || !updateResp.isSucc()) {
                    log.error("公众号授权，更新授权账户异常，RPC接口返回：{}", JSON.toJSONString(updateResp));
                }
            } catch (Exception e) {
                log.error("公众号授权，更新授权账户异常，异常信息：{}", e.getMessage(), e);
            }
        }

        authAccount.put(USERID, userId);
        // 高级授权有效期30天
        bjwte.set(LocalDateTime.now().plusDays(userAuthCfgBean.getMpUerInfoAuthExpire()).toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
        log.info("完成高级授权：{}", JSON.toJSONString(authAccount));

        return true;
    }

    /**
     * 查询用户信息
     * @param mpAppId
     * @param unionId
     * @param openId
     * @return
     */
    private WxOauthUser queryWxOauthUser(String mpAppId, String unionId, String openId) {
        String cacheKey = String.format(CacheConstans.MP_AUTH_ACC_CAHCE, mpAppId, openId);
        WxOauthUser wxOauthUser = redissonService.get(cacheKey, WxOauthUser.class);
        if (wxOauthUser == null) {
            QueryWxOauthUserResp queryResp = rpcWxOauthUserService.queryWxOauthUser(new QueryWxOauthUserReq().setAppId(mpAppId).setOpenId(openId).setUnionId(unionId));
            if (queryResp != null && queryResp.isSucc()) {
                wxOauthUser = queryResp.getWxOauthUser();
                if (wxOauthUser == null) {
                    wxOauthUser = new WxOauthUser();
                } else {
                    redissonService.setJson(cacheKey, wxOauthUser, TimeUnit.HOURS.toMillis(2));
                }
            }
        }

        return wxOauthUser;
    }
    @Override
    public String getChannel() {
        return AuthChannel.MP.getChannel();
    }
}
